using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using EnvDTE;
using EnvDTE80;

namespace Bordecal.ImportsSorter
{
	internal sealed class ImportsDirective : IComparable<ImportsDirective>
	{
		#region Fields

		/// <summary>
		/// Original code for the imports directive.
		/// </summary>
		private readonly string elementCode;

		/// <summary>
		/// "Cleaned" version of the namespace to which the imports directive refers.
		/// </summary>
		private readonly string nameSpace;

		/// <summary>
		/// Index of the sorting group into which the directive should be placed.
		/// </summary>
		private readonly int sortingGroup;

		/// <summary>
		/// Trailing comments to be added to the imports directive line in the code file.
		/// </summary>
		private readonly string trailingComments;

		#endregion

		#region Constructors

		/// <summary>
		/// Initializes a new instance of the ImportsDirective class.
		/// </summary>
		/// <param name="importElement">The CodeImport element that the ImportsDirective instance is to represent.</param>
		/// <param name="trailingComments">Trailing comments to be added to the imports directive line in the code file.</param>
		/// <param name="preparedSortingGroups">The pre-sorted dictionary of namespace prefixes and positions to be used in determining the sorting position of the imports directive.</param>
		internal ImportsDirective(CodeImport importElement, string trailingComments, SortedDictionary<string, int> preparedSortingGroups)
		{
			if (importElement == null)
				throw new ArgumentNullException("importElement");

			this.elementCode = this.ReadElementCode((CodeElement2)importElement);
			this.nameSpace = this.ExtractNamespace(importElement);

			this.sortingGroup = this.FindSortingGroupIndex(this.nameSpace, preparedSortingGroups);

			if (trailingComments == null)
				trailingComments = string.Empty;
			this.trailingComments = trailingComments;
		}

		#endregion

		#region Properties

		/// <summary>
		/// Gets the code for the imports directive.
		/// </summary>
		internal string ElementCode
		{
			get
			{
				return this.elementCode;
			}
		}
		
		/// <summary>
		/// Gets the "cleaned" namespace referenced by the imports directive.
		/// </summary>
		internal string NameSpace
		{
			get
			{
				return this.nameSpace;
			}
		}

		/// <summary>
		/// Gets the index of the sorting group into which the directive should be placed.
		/// </summary>
		internal int SortingGroup
		{
			get
			{
				return this.sortingGroup;
			}
		}

		/// <summary>
		/// Gets any trailing comments that should follow the imports directive in the code file.
		/// </summary>
		internal string TrailingComments
		{
			get
			{
				return this.trailingComments;
			}
		}

		#endregion

		#region Methods

		/// <summary>
		/// Reads the code from a code element.
		/// </summary>
		/// <param name="element">The code element to be read.</param>
		/// <returns>The code text represented by the code element.</returns>
		[SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")]
		private string ReadElementCode(CodeElement2 element)
		{
			if (element == null)
				throw new ArgumentNullException("element");

			EditPoint editPoint = element.StartPoint.CreateEditPoint();
			string retVal = editPoint.GetText(element.EndPoint);
			
			return retVal;
		}

		/// <summary>
		/// Extracts a "clean" namespace representation from a CodeImport element.
		/// </summary>
		/// <remarks>
		/// Any alias and/or qualifer will be stripped from the namespace.
		/// For example, the namespace returned from the imports directive
		/// <c>using GlobalSystem = global::System;</c> would be <c>System</c>.
		/// </remarks>
		/// <param name="importElement">The element from which the namespace should be read.</param>
		/// <returns>The "cleaned" namespace.</returns>
		[SuppressMessage("Microsoft.Performance", "CA1818:DoNotConcatenateStringsInsideLoops")]
		[SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")]
		private string ExtractNamespace(CodeImport importElement)
		{
			string retVal = importElement.Namespace;
			CodeLanguage language = new CodeLanguage(importElement.ProjectItem.Document.Language);

			retVal = retVal.Replace(language.NamespacePartSeparator, ".");

			foreach (string truncationMarker in language.NamespaceTruncationPrefixes)
			{
				int foundIndex = retVal.LastIndexOf(truncationMarker, StringComparison.Ordinal);
				if (foundIndex >= 0)
				{
					string prefix = retVal.Substring(0, foundIndex);
					retVal = retVal.Remove(0, foundIndex + truncationMarker.Length);

					// Add the prefix back at the end for consistent sorting within
					// root namespaces.  e.g.: global::System should sort before
					// MS::System.
					retVal = string.Concat(retVal, ".", prefix);
				}
			}

			return retVal;
		}

		/// <summary>
		/// Determines the index of the sorting group to which a namespace should be assigned.
		/// </summary>
		/// <param name="targetNamespace">The namespace for which the sorting group is to be found.</param>
		/// <param name="preparedSortingGroups">The pre-sorted dictionary of namespace prefixes and positions to be used in determining the sorting position of the namespace.</param>
		/// <returns>The index of the appropriate sorting group for the namespace.</returns>
		[SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")]
		private int FindSortingGroupIndex(string targetNamespace, SortedDictionary<string, int> preparedSortingGroups)
		{
			if (targetNamespace == null)
				throw new ArgumentNullException("targetNamespace");
			
			int? groupIndex = null;

			if ((preparedSortingGroups == null) || (preparedSortingGroups.Count == 0))
			{
				groupIndex = 0;
			}
			else
			{
				foreach (string groupPrefix in preparedSortingGroups.Keys)
				{
					if (groupPrefix.Length > 0)
					{
						if (targetNamespace.Equals(groupPrefix, StringComparison.OrdinalIgnoreCase) ||
							targetNamespace.StartsWith(groupPrefix + ".", StringComparison.OrdinalIgnoreCase))
						{
							groupIndex = preparedSortingGroups[groupPrefix];
							break;
						}
					}
					else
					{
						groupIndex = preparedSortingGroups[groupPrefix];
						break;
					}
				}

				if (groupIndex == null)
					groupIndex = preparedSortingGroups.Count + 1;
			}

			return (int)groupIndex;
		}

		#endregion

		#region IComparable<ImportsStatement> Members

		/// <summary>
		/// Compares the current instance with another ImportsStatement instance.
		/// </summary>
		/// <param name="other">The other ImportsStatement instance to which this one should be compared.</param>
		/// <returns>
		/// 	<list type="table">
		///			<listheader>
		///				<term>Value</term>
		///				<description>Condition</description>
		///			</listheader>
		///			<item>
		///				<term>Less than zero</term>
		///				<description>This instance has a lower sorting group index or namespace than the other instance.</description>
		///			</item>
		///			<item>
		///				<term>Zero</term>
		///				<description>The instances have the same sorting group and namespace.</description>
		///			</item>
		///			<item>
		///				<term>Greater than zero</term>
		///				<description>This instance has a higher sorting group index or namespace than the other instance.</description>
		///			</item>
		///		</list>
		/// </returns>
		public int CompareTo(ImportsDirective other)
		{
			return new ImportsDirectiveComparer().Compare(this, other);
		}

		#endregion
	}
}